package jf.jextn.io;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import jf.jextn.JExtn;

public class IOUtil {
    public static final String UTF_8 = "utf-8";
    public static final Charset CS_UTF_8;
    static {
        CS_UTF_8 = Charset.forName(UTF_8);

        if (CS_UTF_8 == null) {
            throw new InternalError("Charset utf-8 not found on your VM platform.");
        }
    }

    public static final OutputStream NULL_OUTPUT = new OutputStream() {

        @Override
        public void write(int b) throws IOException {
        }
    };

    public static void copyAndClose(InputStream in, OutputStream out) throws IOException {

        copy(in, out);

        out.close();
        in.close();
    }

    public static void copy(InputStream in, OutputStream out) throws IOException {
        byte[] buff = new byte[16 * 1024];
        int read = 0;
        while ((read = in.read(buff)) > -1) {
            out.write(buff, 0, read);
            /*
             *
             * try { if (read == 0) Thread.sleep(500); } catch
             * (InterruptedException e) { // some body has ask us to stop.
             * break; }
             */
        }
    }

    /**
     * Copy the content of reader to writer.
     *
     * @param in
     * @param out
     * @throws IOException
     */
    public static void copy(Reader in, Writer out) throws IOException {
        char[] buff = new char[4 * 1024];
        int read = -1;
        while ((read = in.read(buff)) > -1) {
            out.write(buff, 0, read);
        }
    }

    /**
     * Copy the content of reader to writer and close both reader and writer. It
     * suppress the I/O exception if close operation failed on either reader or
     * writer.
     *
     * @param in
     * @param out
     * @throws IOException
     */

    public static void copyAndClose(Reader in, Writer out) throws IOException {
        copy(in, out);

        closeQuietly(in);
        closeQuietly(out);
    }

    public static void closeQuietly(Closeable closable) {
        try {
            if (closable != null)
                closable.close();
        } catch (IOException e) {
            // shh..
        }
    }

    /**
     * Read the passed input stream and return the array having all data. This
     * method close the input stream after reading all data from it.
     *
     * @param in
     *            the input stream
     * @return content read from the input stream
     * @throws IOException
     */
    public static byte[] toBytes(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream(8 * 1024);

        copyAndClose(in, out);

        return out.toByteArray();
    }

    /**
     * Escape the file name to make it a valid file name. The
     * <code>filename</code> must be name of file and does not include the
     * parent directory path.
     *
     * @param filename
     *            the file name.
     * @return the safe file name
     */
    public static String escapeFileName(String filename) {
        return SAFE_FILENAME.matcher(filename).replaceAll(" ");
    }

    private static final Pattern SAFE_FILENAME = Pattern.compile("[\"'\\\\/\\?\\*<>\\|:]");

    /**
     * @param name
     *            of the file
     * @return file extension prefixed with dot
     */
    public static String getFileExt(String name) {
        int dot = name.lastIndexOf('.');
        String ext = "";
        if (dot > 0)
            ext = name.substring(dot);
        return ext;
    }

    public static void copyAndCloseIn(InputStream in, OutputStream out) throws IOException {
        copy(in, out);
        in.close();
    }

    public static FileFilter filterByFileExtns(String extns) {
        JExtn.checkNull(extns, "Comma separated extns are required");

        return filterByFileExtns(extns.split("\\,"));
    }

    public static FileFilter filterByFileExtns(final String[] extensions) {
        return new FileFilter() {

            @Override
            public boolean accept(File pathname) {
                if (pathname.isDirectory())
                    return true;
                String name = pathname.getName();
                for (String ext : extensions) {
                    if (name.endsWith(ext)) {
                        return true;
                    }
                }
                return false;
            }
        };
    }

    public static void traverse(File fileOrDir, FileVisitor v) throws IOException {
        if (fileOrDir.isDirectory()) {
            FileFilter filter = v.getFilter();
            File[] children = filter == null ? fileOrDir.listFiles() : fileOrDir.listFiles(filter);
            if (!JExtn.isEmpty(children)) {
                for (File file : children) {
                    traverse(file, v);
                }
            }
        } else {
            v.visit(fileOrDir);
        }
    }

    public static void createZip(File directory, File out) throws IOException {
        createZip(directory, out, null);
    }

    public static void createZip(File directory, File out, FileFilter filter) throws IOException {
        FileOutputStream fout = new FileOutputStream(out);

        createZip(directory, filter, fout, -1, -1);
    }

    public static void createZip(final File directory, final FileFilter filter, final OutputStream out,
            final int level, final int method) throws IOException {
        final ZipOutputStream zout = new ZipOutputStream(out);
        if (level > 0) {
            zout.setLevel(level);
        }
        if (method > 0) {
            zout.setMethod(method);
        }

        final String pathPrefix = directory.getCanonicalPath();
        FileVisitor zipEntryCreator = new FileVisitor() {

            @Override
            public void visit(File fileOrDir) throws IOException {
                if (fileOrDir.isFile()) {
                    File file = fileOrDir;

                    ZipEntry entry = new ZipEntry(getEntryName(file));
                    entry.setTime(file.lastModified());

                    zout.putNextEntry(entry);

                    copyAndCloseIn(new FileInputStream(file), zout);
                    zout.closeEntry();
                }
            }

            private String getEntryName(File file) throws IOException {
                String path = file.getCanonicalPath();

                path = path.substring(pathPrefix.length());
                if (File.separatorChar == '\\') {
                    path = path.replaceAll("\\\\", "/");
                }

                return path;
            }

            @Override
            public FileFilter getFilter() {
                return filter;
            }
        };

        traverse(directory, zipEntryCreator);

    }

    public static Reader asReader(String xml) {
        return new StringReader(xml);
    }

}
